﻿namespace EnergyGeneratorSimVisualizer.ViewModels
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using EnergyGeneratorSim;
    using EnergyGeneratorSim.Parameters;
    using EnergyGeneratorSimVisualizer.Serialization;
    using Microsoft.Win32;

    public class MainWindowViewModel : ViewModelBase
    {
        private IAsyncResult cancelToken;
        private bool isCalculating;
        private IList<string> loggingOutput = new ObservableCollection<string>();
        private SynchronizationContext context = SynchronizationContext.Current;
        private double progressReportMaximum;
        private double progressReportCurrentValue;
        private SimulationParameters simulationParametersModel;
        private RaumenergiemotorDFEM simulator;

        public MainWindowViewModel()
        {
            this.ProcessedData = new ObservableCollection<SimulationDataItem>();
            this.ProgressReportCurrentValue = 0;
            this.ProgressReportMaximum = 100;

            var newParameters = CreateExampleParameter();
            this.SetSimulationParameters(newParameters);
        }

        public SimulationParametersViewModel SimulationParameters { get; private set; }

        public void CalculateSimulation()
        {
            this.IsCalculating = true;
            this.loggingOutput.Clear();

            this.ProcessedData = new ObservableCollection<SimulationDataItem>();
            this.NotifyPropertyChanged(x => x.ProcessedData);

            var job = new ThreadStart(this.CalculateSimulationWork);

            cancelToken = job.BeginInvoke(this.CalculateSimulationReport, null);
        }

        public void CancelSimulation()
        {
            lock (this)
            {
                if (this.simulator != null)
                {
                    this.simulator.Cancel = true;
                }
            }
        }

        private static SimulationParameters CreateExampleParameter()
        {
            var param = new SimulationParameters();

            param.AnzP = 100000000;                         // Zum Lösen der Dgl.: Anzahl der tatsächlich berechneten Zeit-Schritte
            param.dt = 43E-9;                               // Sekunden, Zum Lösen der Dgl.: Dauer der Zeitschritte zur iterativen Lsg. der Dgl.
            param.Abstd = 1;                                // Nur für die Vorbereitung, nicht zum Lösen der Dgl.: Jeden wievielten Punkt soll ich plotten ins Excel
            param.PlotAnfang = 0000;                        // Zum Lösen der Dgl.: Erster-Plot-Punkt: Anfang des Daten-Exports nach Excel
            param.PlotEnde = 100000000;                     // Zum Lösen der Dgl.: Letzter-Plot-Punkt: Ende des Daten-Exports nach Excel
            param.PlotStep = 4000;                          // Zum Lösen der Dgl.: Schrittweite des Daten-Exports nach Excel

            param.Inductor.Ninput = 100;                    // Zahl der Wicklungen der Input-Spule
            param.Inductor.Nturbo = 9;                      // Zahl der Wicklungen der Turbo-Spule
            param.Inductor.Nebeninput = 10;                 // Windungen nebeneinander in der Input-Spule
            param.Inductor.Ueberinput = 10;                 // Windungen uebereinander in der Input-Spule
            param.Inductor.Nebenturbo = 3;                  // Windungen nebeneinander in der Turbo-Spule
            param.Inductor.Ueberturbo = 3;                  // Windungen uebereinander in der Turbo-Spule
            param.Inductor.DD = 0.010;                      // Meter, Durchmesser des Spulendrahtes zur Angabe der Drahtstärke

            //  Dauermagnet-Emulation:  Magnetfeld muß nach Messung mit Hall-Sonde eingegeben werden.
            param.PermanentMagnet.Bsw = 1E-2;               // Magnetfeld-Speicherung nach *1 von S.2 in Zentimeter-Schritten
            // Ich emuliere hier das Magnetfeld eines 1T-Magneten durch ein Spulenpaar nach *1 von S.5
            param.PermanentMagnet.MEyo = 0.05;              // y-Koordinaten der Magnetfeld-Emulationsspulen nach *1 von S.5
            param.PermanentMagnet.MEro = 0.01;              // Radius der Magnetfeld-Emulationsspulen nach *1 von S.5  
            param.PermanentMagnet.MEI = 15899.87553474;     // Strom des Magnetfeld-Emulationsspulenpaares nach *1 von S.5, Angabe in Ampere

            //  Allgemeine technische Größen:
            param.General.rho = 1.35E-8;                    // Ohm*m}    {Spez. elektr. Widerstand von Kupfer, je nach Temperatur, Kohlrausch,T193
            param.General.rhoMag = 7.8E3;                   // kg/m^3}  {Dichte des Magnet-Materials, Eisen, Kohlrausch Bd.3
            param.General.CT = 101.7E-6;                    // 150E-6;} {Farad}    {Kapazität des Kondensators, der mit in der Turbo-Spule (!) in Reihe geschaltet
            param.General.CI = 100E-6;                      // Farad}   {Kapazität des Kondensators, der mit in der Input-Spule (!) in Reihe geschaltet
            //  Sonstige (zur Eingabe):
            param.General.Rlast = 0.030;                    // Ohm}   {Ohm'scher Lastwiderstand im LC-Turbo-Schwingkreis
            param.General.UmAn = 30000;                     // U/min} {Anfangsbedingung mechanisch - Rotierender Magnet: Startdrehzahl
            param.General.Uc = 0;                           // Volt}  
            param.General.Il = 0;                           //Ampere} {Anfangsbedingung elektrisch - Kondensatorspannung = 0, Kein Spulenstrom

            //  Mechanische Leistungs-Entnahme (geschwindigkeits-proportional, aber nicht nur Reibung:
            param.General.crAnfang = 45E-6;                 // Koeffizient einer geschwindigkeits-proportionalen Reibung zwecks mechanischer Leistungs-Entnahme
            param.General.phipZiel = 30100;                 // Ziel-Drehzahl, an der die Reibungs-Nachregelung ausgerichtet wird.

            return param;
        }

        private void CalculateSimulationWork()
        {
            lock (this)
            {
                this.simulator = new RaumenergiemotorDFEM();
            }

            this.simulator.LogCallback = this.Log;
            this.simulator.ProgressReportStepCount = 50;
            this.simulator.InitializeProgressReport = this.InitializeProgressReport;
            this.simulator.ProgressReport = this.ProgressReport;
            var result = simulator.Calculate(this.simulationParametersModel);

            this.Data = result.SimulationData;

            var processedData = new List<SimulationDataItem>(1000);

            int stepWidth = result.SimulationData.Length / 1000;

            if (stepWidth > 0)
            {
                for (int i = 0; i < 1000; i++)
                {
                    processedData.Add(result.SimulationData[i * stepWidth]);
                }
            }

            //this.ProcessedData = processedData;

            lock (this)
            {
                this.simulator = null;
            }
        }

        private void CalculateSimulationReport(IAsyncResult data)
        {
            this.NotifyPropertyChanged(x => x.Data);
            this.NotifyPropertyChanged(x => x.ProcessedData);
            this.IsCalculating = false;
        }

        public IEnumerable<SimulationDataItem> Data { get; private set; }

        public ICollection<SimulationDataItem> ProcessedData { get; private set; }

        public bool IsNotCalculating
        {
            get
            {
                return !this.isCalculating;
            }
        }

        public bool IsCalculating
        {
            get
            {
                return this.isCalculating;
            }

            set
            {
                this.isCalculating = value;
                this.NotifyPropertyChanged(x => x.IsCalculating);
                this.NotifyPropertyChanged(x => x.IsNotCalculating);
            }
        }

        public double ProgressReportMaximum
        {
            get
            {
                return this.progressReportMaximum;
            }

            set
            {
                this.progressReportMaximum = value;
                this.NotifyPropertyChanged(x => x.ProgressReportMaximum);
            }
        }

        public double ProgressReportCurrentValue
        {
            get
            {
                return this.progressReportCurrentValue;
            }

            set
            {
                this.progressReportCurrentValue = value;
                this.NotifyPropertyChanged(x => x.ProgressReportCurrentValue);
            }
        }

        public IEnumerable<string> LoggingOutput
        {
            get
            {
                return this.loggingOutput;
            }
        }

        public void SetSimulationParameters(SimulationParameters newParameters)
        {
            if (newParameters == null)
            {
                throw new ArgumentNullException("newParameters");
            }

            this.simulationParametersModel = newParameters;
            this.SimulationParameters = new SimulationParametersViewModel(this.simulationParametersModel);
            this.NotifyPropertyChanged(x => x.SimulationParameters);
        }

        public void SaveSettingsAsync()
        {
            var saveDialog = new SaveFileDialog();
            saveDialog.FileName = GetSettingFileName();
            saveDialog.InitialDirectory = GetSettingPath();

            var result = saveDialog.ShowDialog();

            if (result == null || !result.Value)
            {
                return;
            }

            var copiedParameters = this.simulationParametersModel.Clone();

            var task = new Task(() =>
                {
                    using (var fileStream = saveDialog.OpenFile())
                    using (var fileWriter = new StreamWriter(fileStream, Encoding.UTF8))
                    {
                        Serializator.Serialize(fileWriter, this.SimulationParameters.Model);
                    }
                });
            task.Start();
        }

        public void LoadSettingsAsync()
        {
            var openDialog = new OpenFileDialog();
            openDialog.FileName = GetSettingFileName();
            openDialog.InitialDirectory = GetSettingPath();

            var result = openDialog.ShowDialog();

            if (result == null || !result.Value)
            {
                return;
            }

            var task = new Task<SimulationParameters>(() =>
                {
                    using (var fileStream = openDialog.OpenFile())
                    using (var fileWriter = new StreamReader(fileStream, Encoding.UTF8))
                    {
                        var newParameters = Serializator.Deserialize(fileWriter);
                        return newParameters;
                    }
                });
            task.ContinueWith(t => this.SetSimulationParameters(t.Result));
            task.Start();
        }

        private void Log(string message)
        {
            if (this.context == null)
            {
                this.LogInternal(message);
            }

            this.context.Send(this.LogInternal, message);
        }

        private void LogInternal(object data)
        {
            string message = data.ToString();

            string[] lines = message.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);

            if (lines.Length == 0)
            {
                return;
            }

            // first line is appended to the current line
            var currentLine = string.Empty;

            if (this.loggingOutput.Count > 0)
            {
                currentLine = this.loggingOutput[this.loggingOutput.Count - 1];
                this.loggingOutput.RemoveAt(this.loggingOutput.Count - 1);
            }

            currentLine += lines[0];
            this.loggingOutput.Add(currentLine);

            // add the other lines
            for (int i = 1; i < lines.Length; i++)
            {
                this.loggingOutput.Add(lines[i]);
            }
        }

        private void InitializeProgressReport(ulong stepCount)
        {
            if (this.context == null)
            {
                this.InitializeProgressReportInternal(stepCount);
            }

            this.context.Send(this.InitializeProgressReportInternal, stepCount);
        }

        private void InitializeProgressReportInternal(object data)
        {
            ulong stepCount = (ulong)data;

            this.ProgressReportMaximum = stepCount;
            this.ProgressReportCurrentValue = 0;
        }

        private void ProgressReport(ulong currentValue, SimulationDataItem data)
        {
            if (this.context == null)
            {
                this.ProgressReportInternal(currentValue);
            }

            this.context.Send(this.ProgressReportInternal, new KeyValuePair<ulong, SimulationDataItem>(currentValue, data));
        }

        private void ProgressReportInternal(object data)
        {
            var item = (KeyValuePair<ulong, SimulationDataItem>)data;

            this.ProgressReportCurrentValue = item.Key;
            this.ProcessedData.Add(item.Value);
        }

        private static string GetSettingPath()
        {
            return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        }

        private static string GetSettingFileName()
        {
            return "EnergyGeneratorSim.xml";
        }
    }
}
